package org.saxonextensions.jcr;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.AnyNodeTest;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.Type;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.value.UntypedAtomicValue;
import net.sf.saxon.value.Value;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import java.util.Arrays;
import java.util.Iterator;


/**
 * JCR Node wrapper
 */
public class JCRNodeWrapper implements NodeInfo, VirtualNode, SiblingCountingNode {

  protected Object node;
  protected short nodeKind;
  private JCRNodeWrapper parent;     // null means unknown
  protected JCRDocumentWrapper docWrapper;
  protected int index;            // -1 means unknown

  /**
   * This constructor is protected: nodes should be created using the wrap
   * factory method on the DocumentWrapper class
   *
   * @param node   The JCR node to be wrapped
   * @param parent The NodeWrapper that wraps the parent of this node
   * @param index  Position of this node among its siblings
   */
  protected JCRNodeWrapper(Object node, JCRNodeWrapper parent, int index) {
    this.node = node;
    this.parent = parent;
    this.index = index;
  }

  /**
   * Factory method to wrap a JCR node with a wrapper that implements the Saxon NodeInfo interface.
   *
   * @param node The JCR node
   * @param docWrapper The wrapper for the Document containing this node
   * @return The new wrapper for the supplied node
   */
  protected JCRNodeWrapper makeWrapper(Object node, JCRDocumentWrapper docWrapper) {
    return makeWrapper(node, docWrapper, null, -1);
  }

  /**
   * Factory method to wrap a JCR node with a wrapper that implements the Saxon NodeInfo interface.
   *
   * @param node The JCR node
   * @param docWrapper The wrapper for the Document containing this node
   * @param parent The wrapper for the parent of the JCR node
   * @param index The position of this node relative to its siblings
   * @return The new wrapper for the supplied node
   */
  protected JCRNodeWrapper makeWrapper(Object node, JCRDocumentWrapper docWrapper, JCRNodeWrapper parent, int index) {
    JCRNodeWrapper wrapper;
    if (node instanceof Node) {
      try {
        Node _node = (Node) node;
        if (_node.isSame(_node.getSession().getRootNode())) {
          // return document for the root node
          return docWrapper;
        } else {
          wrapper = new JCRNodeWrapper(node, parent, index);
          wrapper.nodeKind = Type.ELEMENT;
        }
      } catch (RepositoryException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      }
    } else if (node instanceof Property) {
      wrapper = new JCRNodeWrapper(node, parent, index);
      wrapper.nodeKind = Type.ATTRIBUTE;
    } else {
      throw new IllegalArgumentException("Bad node type {" + node.getClass().getName() + "} instance " + node.toString());
    }
    wrapper.docWrapper = docWrapper;
    return wrapper;
  }

  /**
   * Get the underlying DOM node, to implement the VirtualNode interface
   */

  public Object getUnderlyingNode() {
    return node;
  }


  /**
   * Get the name pool for this node
   *
   * @return the NamePool
   */

  public NamePool getNamePool() {
    return docWrapper.getNamePool();
  }

  /**
   * Return the type of node.
   *
   * @return one of the values Node.ELEMENT, Node.TEXT, Node.ATTRIBUTE, etc.
   */
  public int getNodeKind() {
    return nodeKind;
  }

  /**
   * Get the typed value of the item
   */
  public SequenceIterator getTypedValue() {
    return SingletonIterator.makeIterator((AtomicValue) atomize());
  }

  public Value atomize() {
    switch (getNodeKind()) {
      case Type.COMMENT:
      case Type.PROCESSING_INSTRUCTION:
        return new StringValue(getStringValueCS());
      default:
        return new UntypedAtomicValue(getStringValueCS());
    }
  }

  /**
   * Get the type annotation
   *
   * @return UNTYPED or UNTYPED_ATOMIC
   */
  public int getTypeAnnotation() {
    if (getNodeKind() == Type.ATTRIBUTE) {
      return StandardNames.XS_UNTYPED_ATOMIC;
    }
    return StandardNames.XS_UNTYPED;
  }

  /**
   * Determine whether this is the same node as another node. <br />
   * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
   *
   * @param other other node
   * @return true if this Node object and the supplied Node object represent the
   *         same node in the tree.
   */
/*
  public boolean isSameNode(NodeInfo other) {
    if (!(other instanceof JCRNodeWrapper)) {
      return false;
    }
    JCRNodeWrapper ow = (JCRNodeWrapper) other;
    try {
      if (node instanceof Node) {
        return ((Node) node).isSame((Node) ow.getUnderlyingNode());
      } else if (node instanceof Property) {
        return ((Property) node).isSame((Property) ow.getUnderlyingNode());
      }
      throw new RuntimeException("Unknown node type {" + node.getClass().getName() + "}");
    } catch (RepositoryException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
  }
*/

  /**
   * Get the System ID for the node.
   *
   * @return the System Identifier of the entity in the source document containing the node,
   *         or null if not known. Note this is not the same as the base URI: the base URI can be
   *         modified by xml:base, but the system ID cannot.
   */

  public String getSystemId() {
    return docWrapper.baseURI;
  }

  public void setSystemId(String uri) {
    docWrapper.baseURI = uri;
  }

  /**
   * Get the Base URI for the node, that is, the URI used for resolving a relative URI contained in the node.
   */
  public String getBaseURI() {
/*
    if (getNodeKind() == Type.NAMESPACE) {
      return null;
    }
    NodeInfo n = this;
    if (getNodeKind() != Type.ELEMENT) {
      n = n.getParent();
    }
    // Look for an xml:base attribute
    while (n != null) {
      String xmlbase = n.getAttributeValue(StandardNames.XML_BASE);
      if (xmlbase != null) {
        return xmlbase;
      }
      n = n.getParent();
    }
*/
    // if not found, return the base URI of the document node
    return docWrapper.baseURI;
  }

  /**
   * Get line number
   *
   * @return the line number of the node in its original source document; or -1 if not available
   */

  public int getLineNumber() {
    return -1;
  }

  public int getColumnNumber() {
    return 0;
  }

  /**
   * Determine the relative position of this node and another node, in document order.
   * The other node will always be in the same document.
   *
   * @param other The other node, whose position is to be compared with this node
   * @return -1 if this node precedes the other node, +1 if it follows the other
   *         node, or 0 if they are the same node. (In this case, isSameNode() will always
   *         return true, and the two nodes will produce the same result for generateId())
   */

  public int compareOrder(NodeInfo other) {
    return Navigator.compareOrder(this, (SiblingCountingNode) other);
  }

  /**
   * Return the string value of the node. The interpretation of this depends on the type
   * of node. For an element it is the accumulated character content of the element,
   * including descendant elements.
   *
   * @return the string value of the node
   */
  public String getStringValue() {
    if (node instanceof Property) {
      try {
        if (((Property) node).getDefinition().isMultiple()) {
          return Arrays.toString(((Property) node).getValues());
        } else {
          return ((Property) node).getValue().getString();
        }
      } catch (RepositoryException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      }
    } else {
      return "";
    }
  }

  public CharSequence getStringValueCS() {
    return getStringValue();
  }

  /**
   * Get name code. The name code is a coded form of the node name: two nodes
   * with the same name code have the same namespace URI, the same local name,
   * and the same prefix. By masking the name code with &0xfffff, you get a
   * fingerprint: two nodes with the same fingerprint have the same local name
   * and namespace URI.
   *
   * @see net.sf.saxon.om.NamePool#allocate allocate
   */
  public int getNameCode() {
    switch (nodeKind) {
      case Type.ELEMENT:
      case Type.ATTRIBUTE:
      case Type.PROCESSING_INSTRUCTION:
      case Type.NAMESPACE:
        return docWrapper.getNamePool().allocate(getPrefix(), getURI(), getLocalPart());
      default:
        return -1;
    }
  }

  /**
   * Get fingerprint. The fingerprint is a coded form of the expanded name
   * of the node: two nodes
   * with the same name code have the same namespace URI and the same local name.
   * A fingerprint of -1 should be returned for a node with no name.
   */

  public int getFingerprint() {
    return getNameCode() & 0xfffff;
  }

  /**
   * Get the local part of the name of this node. This is the name after the ":" if any.
   *
   * @return the local part of the name. For an unnamed node, returns "".
   */
  public String getLocalPart() {
    try {
      if (node instanceof Node) {
        String nodeName = ((Node) node).getName();
        return nodeName.contains(":") ? nodeName.substring(nodeName.indexOf(":")+1, nodeName.length()) :nodeName;
      } else if (node instanceof Property) {
        String nodeName = ((Property) node).getName();
        return nodeName.contains(":") ? nodeName.substring(nodeName.indexOf(":")+1, nodeName.length()) :nodeName;
      }
    } catch (RepositoryException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    return "";
  }

  /**
   * Get the prefix part of the name of this node. This is the name before the ":" if any.
   * (Note, this method isn't required as part of the NodeInfo interface.)
   *
   * @return the prefix part of the name. For an unnamed node, return an empty string.
   */
  public String getPrefix() {
    try {
      if (node instanceof Node) {
        String nodeName = ((Node) node).getName();
        return nodeName.contains(":") ? nodeName.substring(0, nodeName.indexOf(":")) :"";
      } else if (node instanceof Property) {
        String nodeName = ((Property) node).getName();
        return nodeName.contains(":") ? nodeName.substring(0, nodeName.indexOf(":")) :"";
      }
    } catch (RepositoryException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    return "";
  }

  /**
   * Get the URI part of the name of this node. This is the URI corresponding to the
   * prefix, or the URI of the default namespace if appropriate.
   *
   * @return The URI of the namespace of this node. For an unnamed node, return null.
   *         For a node with an empty prefix, return an empty string.
   */
  public String getURI() {
    final String prefix = getPrefix();
    if (!"".equals(prefix)) {
      final NamePool namePool = getNamePool();
      return namePool.getURIFromNamespaceCode(namePool.getCodeForPrefix(prefix));
    }
    return "";
  }

  /**
   * Get the display name of this node. For elements and attributes this is [prefix:]localname.
   * For unnamed nodes, it is an empty string.
   *
   * @return The display name of this node.
   *         For a node with no name, return an empty string.
   */
  public String getDisplayName() {
    String prefix = getPrefix();
    return prefix.length()>0 ?prefix + ":" + getLocalPart() :getLocalPart();
  }

  /**
   * Get the NodeInfo object representing the parent of this node
   */
  public NodeInfo getParent() {
    if (parent == null) {
      if (node instanceof Node) {
        try {
          if (((Node) node).isSame(((Node) node).getSession().getRootNode())) {
            parent = null;
          } else {
            parent = makeWrapper(((Node) node).getParent(), docWrapper);
          }
        } catch (RepositoryException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      } else if (node instanceof Property) {
        try {
          parent = makeWrapper(((Property) node).getParent(), docWrapper);
        } catch (RepositoryException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      } else {
        throw new IllegalStateException("Unknown node type {" + node.getClass().getName() + "}");
      }
    }
    return parent;
  }

  /**
   * Get the index position of this node among its siblings (starting from 0)
   * @TODO it can be improved by delegating to JCR
   */
  public int getSiblingPosition() {
    if (index == -1) {
      int ix = 0;
      getParent();
      AxisIterator iter;
      switch (nodeKind) {
        case Type.ELEMENT:
        case Type.TEXT:
        case Type.COMMENT:
        case Type.PROCESSING_INSTRUCTION:
          iter = parent.iterateAxis(Axis.CHILD);
          break;
        case Type.ATTRIBUTE:
          iter = parent.iterateAxis(Axis.ATTRIBUTE);
          break;
        case Type.NAMESPACE:
          iter = parent.iterateAxis(Axis.NAMESPACE);
          break;
        default:
          index = 0;
          return index;
      }
      while (true) {
        NodeInfo n = (NodeInfo) iter.next();
        if (n == null) {
          break;
        }
        if (n.isSameNodeInfo(this)) {
          index = ix;
          return index;
        }
        ix++;
      }
      throw new IllegalStateException("Node is not linked to the parent node");
    }
    return index;
  }

  /**
   * Return an iteration over the nodes reached by the given axis from this node
   *
   * @param axisNumber the axis to be used
   * @return a SequenceIterator that scans the nodes reached by the axis in turn.
   */
  public AxisIterator iterateAxis(byte axisNumber) {
    return iterateAxis(axisNumber, AnyNodeTest.getInstance());
  }

  /**
   * Return an iteration over the nodes reached by the given axis from this node
   *
   * @param axisNumber the axis to be used
   * @param nodeTest   A pattern to be matched by the returned nodes
   * @return a SequenceIterator that scans the nodes reached by the axis in turn.
   */
  public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) {
    switch (axisNumber) {
      case Axis.ANCESTOR:
        if (nodeKind == Type.DOCUMENT) {
          return EmptyIterator.getInstance();
        }
        return new Navigator.AxisFilter(
          new Navigator.AncestorEnumeration(this, false),
          nodeTest);

      case Axis.ANCESTOR_OR_SELF:
        if (nodeKind == Type.DOCUMENT) {
          return Navigator.filteredSingleton(this, nodeTest);
        }
        return new Navigator.AxisFilter(
          new Navigator.AncestorEnumeration(this, true),
          nodeTest);

      case Axis.ATTRIBUTE:
        if (nodeKind != Type.ELEMENT) return EmptyIterator.getInstance();
        return new Navigator.AxisFilter(
          new AttributeEnumeration(this),
          nodeTest);

      case Axis.CHILD:
        if (hasChildNodes()) {
          return new Navigator.AxisFilter(
            new ChildEnumeration(this, true, true),
            nodeTest);
        } else {
          return EmptyIterator.getInstance();
        }

      case Axis.DESCENDANT:
        if (hasChildNodes()) {
          return new Navigator.AxisFilter(
            new Navigator.DescendantEnumeration(this, false, true),
            nodeTest);
        } else {
          return EmptyIterator.getInstance();
        }

      case Axis.DESCENDANT_OR_SELF:
        return new Navigator.AxisFilter(
          new Navigator.DescendantEnumeration(this, true, true),
          nodeTest);

      case Axis.FOLLOWING:
        return new Navigator.AxisFilter(
          new Navigator.FollowingEnumeration(this),
          nodeTest);

      case Axis.FOLLOWING_SIBLING:
        switch (nodeKind) {
          case Type.DOCUMENT:
          case Type.ATTRIBUTE:
          case Type.NAMESPACE:
            return EmptyIterator.getInstance();
          default:
            return new Navigator.AxisFilter(
              new ChildEnumeration(this, false, true),
              nodeTest);
        }

      case Axis.NAMESPACE:
        if (nodeKind != Type.ELEMENT) {
          return EmptyIterator.getInstance();
        }
        return new Navigator.AxisFilter(
          new NamespaceEnumeration(this),
          nodeTest);

      case Axis.PARENT:
        getParent();
        return Navigator.filteredSingleton(parent, nodeTest);

      case Axis.PRECEDING:
        return new Navigator.AxisFilter(
          new Navigator.PrecedingEnumeration(this, false),
          nodeTest);

      case Axis.PRECEDING_SIBLING:
        switch (nodeKind) {
          case Type.DOCUMENT:
          case Type.ATTRIBUTE:
          case Type.NAMESPACE:
            return EmptyIterator.getInstance();
          default:
            return new Navigator.AxisFilter(
              new ChildEnumeration(this, false, false),
              nodeTest);
        }

      case Axis.SELF:
        return Navigator.filteredSingleton(this, nodeTest);

      case Axis.PRECEDING_OR_ANCESTOR:
        return new Navigator.AxisFilter(
          new Navigator.PrecedingEnumeration(this, true),
          nodeTest);

      default:
        throw new IllegalArgumentException("Unknown axis number " + axisNumber);
    }
  }

  /**
   * Find the value of a given attribute of this node. <BR>
   * This method is defined on all nodes to meet XSL requirements, but for nodes
   * other than elements it will always return null.
   * @param uri the namespace uri of an attribute ("" if no namespace)
   * @param localName the local name of the attribute
   * @return the value of the attribute, if it exists, otherwise null
   */

//    public String getAttributeValue(String uri, String localName) {
//        if (nodeKind==Type.ELEMENT) {
//            Namespace ns = Namespace.getNamespace(uri);
//            return ((Element)node).getAttributeValue(localName, ns);
//        } else {
//            return "";
//        }
//    }

  /**
   * Get the value of a given attribute of this node
   *
   * @param fingerprint The fingerprint of the attribute name
   * @return the attribute value if it exists or null if not
   */
  public String getAttributeValue(int fingerprint) {
    throw new RuntimeException("Not implemented yet.");
/*
        if (nodeKind==Type.ELEMENT) {
            Iterator list = ((Element)node).attributes().iterator();
            NamePool pool = docWrapper.getNamePool();
            while (list.hasNext()) {
                Attribute att = (Attribute)list.next();
                int nameCode = pool.allocate(att.getNamespacePrefix(),
                                             att.getNamespaceURI(),
                                             att.getName());
                if (fingerprint == (nameCode & 0xfffff)) {
                    return att.getValue();
                }
            }
        }
    return null;
*/
  }

  /**
   * Get the root node - always a document node with this implementation
   *
   * @return the NodeInfo representing the containing document
   */
  public NodeInfo getRoot() {
    return docWrapper;
  }

  /**
   * Get the root (document) node
   *
   * @return the DocumentInfo representing the containing document
   */
  public DocumentInfo getDocumentRoot() {
    return docWrapper;
  }

  /**
   * Determine whether the node has any children.
   * Note: the result is equivalent to
   * <tt>getEnumeration(Axis.CHILD, AnyNodeTest.getInstance()).hasNext()</tt>
   */
  public boolean hasChildNodes() {
    switch (nodeKind) {
      case Type.DOCUMENT:
        return true;
      case Type.ELEMENT:
        try {
          return ((Node) node).getNodes().hasNext();
        } catch (RepositoryException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      default:
        return false;
    }
  }

  /**
   * Get a character string that uniquely identifies this node.
   * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
   *
   * @param buffer a Buffer to contain a string that uniquely identifies this node, across all
   *               documents
   */
  public void generateId(FastStringBuffer buffer) {
    Navigator.appendSequentialKey(this, buffer, true);
  }

  /**
   * Get the document number of the document containing this node. For a free-standing
   * orphan node, just return the hashcode.
   */
  public int getDocumentNumber() {
    return getParent().getDocumentNumber();
  }

  /**
   * Copy this node to a given outputter (deep copy)
   */
  public void copy(Receiver out, int whichNamespaces, boolean copyAnnotations, int locationId) throws XPathException {
    Navigator.copy(this, out, docWrapper.getNamePool(), whichNamespaces, copyAnnotations, locationId);
  }

  ///////////////////////////////////////////////////////////////////////////////
  // Axis enumeration classes
  ///////////////////////////////////////////////////////////////////////////////

  private final class AttributeEnumeration extends Navigator.BaseEnumeration {

    private Iterator atts;
    private int ix = 0;
    private JCRNodeWrapper start;

    public AttributeEnumeration(JCRNodeWrapper start) {
      this.start = start;
      try {
        atts = ((Node) start.node).getProperties();
      } catch (RepositoryException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      }
    }

    public void advance() {
      if (atts.hasNext()) {
        current = makeWrapper(atts.next(), docWrapper, start, ix++);
      } else {
        current = null;
      }
    }

    public SequenceIterator getAnother() {
      return new AttributeEnumeration(start);
    }

  }  // end of class AttributeEnumeration

  private final class NamespaceEnumeration extends Navigator.BaseEnumeration {

//    private HashMap nslist = new HashMap();
//    private Iterator prefixes;
    private int ix = 0;
    private JCRNodeWrapper start;

    public NamespaceEnumeration(JCRNodeWrapper start) {
      this.start = start;
//      JCRNodeWrapper curr = start;

      // build the complete list of namespaces

/*
      do {
        Element elem = (Element) curr.node;
        Namespace ns = elem.getNamespace();
        String prefix = ns.getPrefix();
        String uri = ns.getURI();
        if (!(prefix.equals("") && uri.equals(""))) {
          if (!nslist.containsKey(prefix)) {
            nslist.put(ns.getPrefix(), ns);
          }
        }
        List addl = elem.additionalNamespaces();
        if (addl.size() > 0) {
          Iterator itr = addl.iterator();
          while (itr.hasNext()) {
            ns = (Namespace) itr.next();
            if (!nslist.containsKey(ns.getPrefix())) {
              nslist.put(ns.getPrefix(), ns);
            }
          }
        }
        curr = (JCRNodeWrapper) curr.getParent();
      } while (curr.getNodeKind() == Type.ELEMENT);

      nslist.put("xml", Namespace.XML_NAMESPACE);
      prefixes = nslist.keySet().iterator();
*/
      //advance();
    }

    public void advance() {
      throw new RuntimeException("Not implemented yet.");
/*
      if (prefixes.hasNext()) {
        String prefix = (String) prefixes.next();
        Namespace ns = (Namespace) nslist.get(prefix);
        current = makeWrapper(ns, docWrapper, start, ix++);
      } else {
        current = null;
      }
*/
    }

    public SequenceIterator getAnother() {
      return new NamespaceEnumeration(start);
    }

    // NB: namespace nodes in the DOM4J implementation do not support all
    // XPath functions, for example namespace nodes have no parent.


  }  // end of class NamespaceEnumeration


  /**
   * The class ChildEnumeration handles not only the child axis, but also the
   * following-sibling and preceding-sibling axes. It can also iterate the children
   * of the start node in reverse order, something that is needed to support the
   * preceding and preceding-or-ancestor axes (the latter being used by xsl:number)
   */

  private final class ChildEnumeration extends Navigator.BaseEnumeration {

    private JCRNodeWrapper start;
    private JCRNodeWrapper commonParent;
    private NodeIterator children;
    private int ix = 0;
    private boolean downwards;  // iterate children of start node (not siblings)
    private boolean forwards;   // iterate in document order (not reverse order)

    public ChildEnumeration(JCRNodeWrapper start, boolean downwards, boolean forwards) {
      this.start = start;
      this.downwards = downwards;
      this.forwards = forwards;

      if (downwards) {
        commonParent = start;
      } else {
        commonParent = (JCRNodeWrapper) start.getParent();
      }

/*
      if (commonParent.getNodeKind() == Type.DOCUMENT) {
        children = ((Document) commonParent.node).content().listIterator();
      } else {
        children = ((Element) commonParent.node).content().listIterator();
      }
*/
      try {
        children = ((Node) commonParent.node).getNodes();
      } catch (RepositoryException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      }

      if (downwards) {
        if (!forwards) {
          // backwards enumeration: go to the end
          while (children.hasNext()) {
            children.next();
            ix++;
          }
        }
      } else {
        ix = start.getSiblingPosition();
        // find the start node among the list of siblings
        if (forwards) {
          for (int i = 0; i <= ix; i++) {
            children.next();
          }
          ix++;
        } else {
          for (int i = 0; i < ix; i++) {
            children.next();
          }
          ix--;
        }
      }
      //advance();
    }


    public void advance() {
      if (forwards) {
        if (children.hasNext()) {
          Object nextChild = children.next();
/*
          if (nextChild instanceof DocumentType || nextChild instanceof Namespace) {
            advance();
            return;
          }
*/
/*
          if (nextChild instanceof Entity) {
            throw new IllegalStateException("Unexpanded entity in DOM4J tree");
          } else {
//                        if (isAtomizing()) {
//                            current = new UntypedAtomicValue(getStringValue(node));
//                        } else {
            current = makeWrapper(nextChild, docWrapper, commonParent, ix++);
//                        }
          }
*/
          current = makeWrapper(nextChild, docWrapper, commonParent, ix++);
        } else {
          current = null;
        }
      } else {    // backwards
        throw new RuntimeException("Backwards is not implemented yet.");
/*
        if (children.hasPrevious()) {
          Object nextChild = children.previous();
          if (nextChild instanceof DocumentType || nextChild instanceof Namespace) {
            advance();
            return;
          }
          if (nextChild instanceof Entity) {
            throw new IllegalStateException("Unexpanded entity in DOM4J tree");
          } else {
//                        if (isAtomizing()) {
//                            current = new UntypedAtomicValue(getStringValue(node));
//                        } else {
            current = makeWrapper(nextChild, docWrapper, commonParent, ix--);
//                        }
          }
        } else {
          current = null;
        }
*/
      }
    }

    public SequenceIterator getAnother() {
      return new ChildEnumeration(start, downwards, forwards);
    }

  } // end of class ChildEnumeration


  /**
   * Determine whether this is the same node as another node.
   * Note: a.isSameNodeInfo(b) if and only if generateId(a)==generateId(b).
   * This method has the same semantics as isSameNode() in DOM Level 3, but
   * works on Saxon NodeInfo objects rather than DOM Node objects.
   *
   * @param other the node to be compared with this node
   * @return true if this NodeInfo object and the supplied NodeInfo object represent
   *         the same node in the tree.
   */
  public boolean isSameNodeInfo(NodeInfo other) {
    if (!(other instanceof JCRNodeWrapper)) {
      return false;
    }
    try {
      JCRNodeWrapper otherNodeWrapper = (JCRNodeWrapper) other;
      if (node instanceof Node && otherNodeWrapper.node instanceof Node) {
        return ((Node)node).isSame((Node) otherNodeWrapper.node);
      } else if(node instanceof Property && otherNodeWrapper.node instanceof Property) {
        return ((Property)node).isSame((Property)otherNodeWrapper.node);
      }
    } catch (RepositoryException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    return false;
  }

  public Configuration getConfiguration() {
    return docWrapper.getConfiguration();
  }

  /**
   * Get all namespace undeclarations and undeclarations defined on this element.
   *
   * @param buffer If this is non-null, and the result array fits in this buffer, then the result
   *               may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
   * @return An array of integers representing the JCR namespaces.
   */
  public int[] getDeclaredNamespaces(int[] buffer) {
    // return JCR namespaces
/*
    if (!"".equals(getPrefix())) {
      buffer[0] = getNamePool().getCodeForPrefix(getPrefix());
    }
*/
    return EMPTY_NAMESPACE_LIST;
  }

  public boolean isId() {
    return false;
  }

  public boolean isIdref() {
    return false;
  }

  public boolean isNilled() {
    return false;
  }

  /**
   * Output all namespace nodes associated with this element. Does nothing if
   * the node is not an element.
   *
   * @param out              The relevant outputter
   * @param includeAncestors True if namespaces declared on ancestor elements must
   */
/*
  public void sendNamespaceDeclarations(Receiver out, boolean includeAncestors) throws XPathException {
    Navigator. sendNamespaceDeclarations(this, out, includeAncestors);
  }
*/
}